package org.spring.bean.factory.support;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.spring.bean.exceptions.BeanDefinitionStoreException;
import org.spring.bean.exceptions.BeansException;
import org.spring.bean.factory.config.BeanDefinition;
import org.spring.bean.factory.config.ConfigurableListableBeanFactory;
import org.tool.core.lang.Assert;

import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * Spring 的 {@link ConfigurableListableBeanFactory} 和 {@link BeanDefinitionRegistry} 接口的默认实现：
 * 基于 bean 定义元数据的成熟 bean 工厂，可通过后处理器进行扩展。
 *
 * <p>典型用法是在访问 bean 之前首先注册所有 bean 定义（可能从 bean 定义文件中读取）。
 * 因此，按名称查找 Bean 是本地 bean 定义表中的一种廉价操作，它对预先解析的 bean 定义元数据对象进行操作。
 *
 * <p>请注意，特定 bean 定义格式的读取器通常是单独实现的，而不是作为 bean 工厂子类：
 * 例如参见 {@link org.springframework.beans.factory.xml.XmlBeanDefinitionReader}。
 *
 * <p>对于 {@link org.springframework.beans.factory.ListableBeanFactory} 接口的替代实现，
 * 请查看 {@link StaticListableBeanFactory}，它管理现有的 bean 实例，而不是基于 bean 定义创建新实例。
 *
 * @author 李坤
 * @date 2022/4/20 22:11
 */
public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFactory
        implements ConfigurableListableBeanFactory, BeanDefinitionRegistry {

    private final Logger logger = LoggerFactory.getLogger(DefaultListableBeanFactory.class);

    /**
     * bean 定义对象的映射，以 bean 名称为键。
     */
    private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(256);

    /**
     * 单例bean名称的映射，根据依赖项类型进行键控。
     */
    private final Map<Class<?>, String[]> singletonBeanNamesByType = new ConcurrentHashMap<>(64);

    @Override
    public BeanDefinition getBeanDefinition(String beanName) throws BeansException {
        Assert.hasText(beanName, "beanName 不能为空");

        logger.debug("开始获取名称为 {} 的 BeanDefinition", beanName);

        final BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);

        Assert.notNull(beanDefinition, String.format("不存在名称为 %s 的 BeanDefinition", beanName));

        return beanDefinition;
    }

    @Override
    public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition) throws BeanDefinitionStoreException {
        Assert.hasText(beanName, "beanName 不能为空");
        Assert.notNull(beanDefinition, "BeanDefinition 不能为空");

        logger.debug("开始注册名称为 {} 的 BeanDefinition", beanName);

        beanDefinitionMap.put(beanName, beanDefinition);
    }

    @Override
    public boolean containsBeanDefinition(String beanName) {
        return beanDefinitionMap.containsKey(beanName);
    }

    @Override
    public boolean isBeanNameInUse(String beanName) {
        return containsBeanDefinition(beanName);
    }

    @Override
    public int getBeanDefinitionCount() {
        return beanDefinitionMap.size();
    }

    @Override
    public void preInstantiateSingletons() throws BeansException {
        for (String beanName : beanDefinitionMap.keySet()) {
            final Object bean = getBean(beanName);

            logger.debug("创建 bean 实例 {} of preInstantiateSingletons", bean);
        }
    }

    @Override
    public String[] getBeanDefinitionNames() {
        return beanDefinitionMap.keySet().toArray(new String[0]);
    }

    @Override
    public String[] getBeanNamesForType(Class<?> type) {
        return getBeanNamesForType(type, true, true);
    }

    @Override
    public String[] getBeanNamesForType(Class<?> type, boolean includeNonSingletons, boolean allowEagerInit) {
        Assert.notNull(type, "type 不能为空");

        return getBeanNamesOfType4Cache(type);
    }

    @Override
    public <T> Map<String, T> getBeansOfType(Class<T> type) throws BeansException {
        return getBeansOfType(type, true, true);
    }

    @Override
    public <T> Map<String, T> getBeansOfType(Class<T> type, boolean includeNonSingletons, boolean allowEagerInit) throws BeansException {
        final String[] beanNamesForType = getBeanNamesForType(type, includeNonSingletons, allowEagerInit);

        return Arrays.stream(beanNamesForType).collect(Collectors.toMap(Function.identity(),
                beanName -> getBean(beanName, type), (oldValue, newValue) -> newValue, HashMap::new));
    }

    /*-------------------------------------------------private methods------------------------------------------------*/

    private String[] getBeanNamesOfType4Cache(Class<?> clazz) {
        String[] beanNames;
        if (singletonBeanNamesByType.containsKey(clazz)) {
            beanNames = singletonBeanNamesByType.get(clazz);
        } else {
            beanNames = Arrays.stream(getBeanDefinitionNames()).filter(beanDefinitionName ->
                            clazz.isAssignableFrom(getBeanDefinition(beanDefinitionName).getBeanClass()))
                    .toArray(String[]::new);
            singletonBeanNamesByType.put(clazz, beanNames);
        }
        return beanNames;
    }

}
